package top.stmo.spring.aop.test1;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import top.stmo.spring.aop.test1.config.SpringConfig;
import top.stmo.spring.aop.test1.dao.BookDao;

/**
 *      AOP 面向切面编程，一种编程范式，指导开发者如何组织程序结构
 *
 *      AOP是在不改原有代码的前提下对其进行增强
 *
 *      具体实现:
 *          1.导入坐标(pom.xml)
 *          2.制作连接点(原始操作，Dao接口与实现类)
 *          3.制作共性功能(通知类与通知)
 *          4.定义切入点
 *          5.绑定切入点与通知关系(切面)
 *
 *   1.aop包下建立aop配置类,加上 @Component 和 @Aspect 注解
 *   2.配置AOP切入点 和 AOP切入点表达式,切入点表达式用 @Pointcut 注解
 *      切入点:要进行增强的方法
 *      切入点表达式:要进行增强的方法的描述方式
 *   3.编写通知类 和 通知类型,并给通知类型注解的value赋值,值为配置的AOP切入点名
 *      @Before                 前置通知
 *      @After                  后置通知
 *      @Around                 环绕通知
 *      @AfterReturning         返回后通知
 *      @AfterThrowing          抛出异常后通知
 *
 *      其中环绕通知中:
 *      Object ret = pjp.proceed();     表示对原始方法的调用以及原始方法的返回值
 *      环绕通知可以有返回值,Object类型,可以没有返回值void
 *
 *   非环绕通知获取原始方法参数,在方法上添加参数JoinPoint     public void before(JoinPoint jp)
 *   再用Object[] args = jp.getArgs()          获取参数数组
 *
 *   环绕通知使用的是ProceedingJoinPoint，因为ProceedingJoinPoint是JoinPoint类的子类
 *   直接通过  Object[] args = pjp.getArgs()   获取
 *
 *   参数通过for循环,在 .class .equals 与 其他类型.class  即可比较是否为同一类型
 *
 *
 *   ---------------------------------AOP工作流程--------------------------------
 *
 *   初始化bean
 *   判定bean对应的类中的方法是否匹配到任意切入点
 *      注意第1步在容器启动的时候，bean对象还没有被创建成功。
 *      要被实例化bean对象的类中的方法和切入点进行匹配
 *   匹配失败，创建原始对象,如 UserDao
 *      匹配失败说明不需要增强，直接调用原始对象的方法即可。
 *   匹配成功，创建原始对象（目标对象）的代理对象,如: BookDao
 *      匹配成功说明需要对其进行增强
 *      对哪个类做增强，这个类对应的对象就叫做目标对象
 *      因为要对目标对象进行功能增强，而采用的技术是动态代理，所以会为其创建一个代理对象
 *      最终运行的是代理对象的方法，在该方法中会对原始方法进行功能增强
 *
 *   AOP的核心概念
 *       目标对象、连接点、切入点
 *       通知类、通知
 *       切面
 *       代理
 *   SpringAOP的本质或者可以说底层实现是通过代理模式
 *
 *   ---------------------------------AOP切入点表达式--------------------------------
 *
 *   切入点:要进行增强的方法
 *   切入点表达式:要进行增强的方法的描述方式
 *
 *   切入点表达式标准格式：动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名）
 *   execution(public User com.itheima.service.UserService.findById(int))
 *
 *   execution：             动作关键字，描述切入点的行为动作，例如execution表示执行到指定切入点
 *   public:                 访问修饰符,还可以是public，private等，可以省略
 *   User：                  返回值，写返回值类型
 *   com.itheima.service：   包名，多级包使用点连接
 *   UserService:            类/接口名称
 *   findById：              方法名
 *   int:                    参数，直接写参数的类型，多个类型用逗号隔开
 *   异常名：                  方法定义中抛出指定异常，可以省略
 *
 *   我们使用通配符描述切入点，主要的目的就是简化之前的配置
 *
 *   * :单个独立的任意符号，可以独立出现，也可以作为前缀或者后缀的匹配符出现
 *
 *   ..：多个连续的任意符号，可以独立出现，常用于简化包名与参数的书写
 *
 *   +：专用于匹配子类类型
 *
 *   书写技巧:
 *      所有代码按照标准规范开发，否则以下技巧全部失效
 *      描述切入点通常描述接口，而不描述实现类,如果描述到实现类，就出现紧耦合了
 *      访问控制修饰符针对接口开发均采用public描述（可省略访问控制修饰符描述）
 *      返回值类型对于增删改类使用精准类型加速匹配，对于查询类使用*通配快速描述
 *      包名书写尽量不使用..匹配，效率过低，常用*做单个包描述匹配，或精准匹配
 *      接口名/类名书写名称与模块相关的采用*匹配，例如UserService书写成*Service，绑定业务
 *      层接口名
 *      方法名书写以动词进行精准匹配，名词采用匹配，例如getById书写成getBy,selectAll书写成
 *      selectAll
 *      参数规则较为复杂，根据业务方法灵活调整
 *      通常不使用异常作为匹配规则
 *
 *    ---------------------------------AOP通知类型--------------------------------
 *
 *    共提供了5种通知类型:
 *      前置通知
 *      后置通知
 *      环绕通知(重点)
 *      返回后通知(了解)
 *      抛出异常后通知(了解)
 *
 *      @Before                 前置通知
 *
 *      @After                  后置通知
 *
 *      @Around                 环绕通知
 *
 *      @AfterReturning         返回后通知       当前通知方法在原始切入点方法正常执行完毕后执行
 *
 *      @AfterThrowing          抛出异常后通知    当前通知方法在原始切入点方法运行抛出异常后执行
 *
 * 环绕通知注意事项:
 *  环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用，进而实现原始方法调用前后同时添加通知
 *  通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
 *  对原始方法的调用可以不接收返回值，通知方法设置成void即可，如果接收返回值，最好设定为Object类型
 *  原始方法的返回值如果是void类型，通知方法的返回值类型可以设置成void,也可以设置成Object
 *  由于无法预知原始方法运行后是否会抛出异常，因此环绕通知方法必须要处理Throwable异常
 *
 *  获取切入点方法的参数，所有的通知类型都可以获取参数
 *      JoinPoint：适用于前置、后置、返回后、抛出异常后通知
 *      ProceedingJoinPoint：适用于环绕通知
 *
 *   ---------------------------------AOP总结--------------------------------
 *
 *   概念：AOP面向切面编程，一种编程范式
 *      作用：在不惊动原始设计的基础上为方法进行功能增强
 *   核心概念
 *      代理（Proxy）：SpringAOP的核心本质是采用代理模式实现的
 *      连接点（JoinPoint）：在SpringAOP中，理解为任意方法的执行
 *      切入点（Pointcut）：匹配连接点的式子，也是具有共性功能的方法描述
 *      通知（Advice）：若干个方法的共性功能，在切入点处执行，最终体现为一个方法
 *      切面（Aspect）：描述通知与切入点的对应关系
 *      目标对象（Target）：被代理的原始对象成为目标对象
 *
 *
 *    切入点表达式标准格式：动作关键字(访问修饰符 返回值 包名.类/接口名.方法名（参数）异常名)
 *          execution(* com.itheima.service.*Service.*(..))
 *    切入点表达式描述通配符：
 *      作用：用于快速描述，范围描述
 *          *：匹配任意符号（常用）
 *          .. ：匹配多个连续的任意符号（常用）
 *          +：匹配子类类型
 *    切入点表达式书写技巧
 *      1.按标准规范开发
 *      2.查询操作的返回值建议使用*匹配
 *      3.减少使用..的形式描述包
 *      4.对接口进行描述，使用*表示模块名，例如UserService的匹配描述为*Service
 *      5.方法名书写保留动词，例如get，使用*表示名词，例如getById匹配描述为getBy*
 *      6.参数根据实际情况灵活调整
 *
 *    五种通知类型
 *      前置通知
 *      后置通知
 *      环绕通知（重点）
 *          环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
 *          环绕通知可以隔离原始方法的调用执行
 *          环绕通知返回值设置为Object类型
 *          环绕通知中可以对原始方法调用过程中出现的异常进行处理
 *      返回后通知
 *      抛出异常后通知
 *
 *    通知中获取参数
 *      获取切入点方法的参数，所有的通知类型都可以获取参数
 *          JoinPoint：适用于前置、后置、返回后、抛出异常后通知
 *          ProceedingJoinPoint：适用于环绕通知
 *    获取切入点方法返回值，前置和抛出异常后通知是没有返回值，后置通知可有可无，所以不做研究
 *          返回后通知
 *          环绕通知
 *    获取切入点方法运行异常信息，前置和返回后通知是不会有，后置通知可有可无，所以不做研究
 *          抛出异常后通知
 *          环绕通知
 *
 */
public class App1 {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
//        bookDao.update();
        System.out.println(bookDao.getClass());
    }
}
